package com.gxp.util.security;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.math.BigInteger;
import java.net.URLDecoder;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.Calendar;

import javax.crypto.Cipher;

import org.apache.commons.codec.binary.Hex;
import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

/**
 * 
 * @Package: com.jb.framework.filter<br>
 * @ClassName: RSAUtil<br>
 * @Description: RSA加密工具类，这里我们是每次系统启动时声称一套公钥，私钥，因此不能将加密串存入数据库中，如果要这么做可以预先生成密钥队写入到文件中<br>
 */
public class RSAUtil {
	
	private RSAUtil(){}
	
	public static final String keyPubFile = "rsaPubKey.bin";
	public static final String keyPriFile = "rsaPriKey.bin";
	
	private static RSAUtil rsa;
	//密钥生成器
	private  PublicKey publicKey;
	//密钥队
	private  PrivateKey privateKey; 
	
	
	public static RSAUtil getInstance(){
		synchronized ("rsa") {
			if(rsa == null) {
				rsa = new RSAUtil();
				rsa.init();
			}
		}
		return rsa;
	}
	/**
	 * 
	 * @Title: init
	 * @Description: 初始化方法
	 * void
	 * @throws
	 */
	private void init() {
		//构建RSA算法
		try {
			KeyPairGenerator kengen = KeyPairGenerator.getInstance("RSA",new BouncyCastleProvider());
			//构建随机种子
			SecureRandom random = new SecureRandom();
			random.setSeed(Calendar.getInstance().getTimeInMillis());
			kengen.initialize(512, random);//采用512位加密
			KeyPair keyPair = kengen.generateKeyPair();
			publicKey = keyPair.getPublic();
			privateKey = keyPair.getPrivate();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
	}
	/**
	 * 
	 * @Title: getPublicKey
	 * @Description: 获取公钥
	 * @return
	 * PublicKey
	 * @throws
	 */
	public PublicKey getPublicKey(){
		return this.publicKey;
	}
	/**
	 * 
	 * @Title: getPrivateKey
	 * @Description: 获取私钥
	 * @return
	 * PrivateKey
	 * @throws
	 */
	public PrivateKey getPrivateKey(){
		return this.privateKey;
	}
	/**
	 * 
	 * @Title: getPublicKeyStr
	 * @Description: 获取系统公钥字符串，前6位为exponentk,后面为modlus
	 * @return
	 * String
	 * @throws
	 */
	public String getPublicKeyStr(){
		BCRSAPublicKey pk = (BCRSAPublicKey) getPublicKey();
		String pubStr = "";
		pubStr += b2hex(pk.getPublicExponent().toByteArray());
		pubStr += b2hex(pk.getModulus().toByteArray());
		return pubStr;
	}
	public String getPublicKeyExp() {
		BCRSAPublicKey pk = (BCRSAPublicKey) getPublicKey();
		return b2hex(pk.getPublicExponent().toByteArray());
	}
	public String getPublicMod() {
		BCRSAPublicKey pk = (BCRSAPublicKey) getPublicKey();
		return b2hex(pk.getModulus().toByteArray());
	}
	/**
	 * 
	 * @Title: entryText
	 * @Description: 使用默认公钥进行加密
	 * @param text
	 * @return
	 * String
	 * @throws
	 */
	public String encryText(String text) {
		return encryText(text,getPublicKey());
	}
	/**
	 * 
	 * @Title: entryText
	 * @Description: 使用指定公钥进行加密，解决长字符串加密
	 * @param text
	 * @param publicKey2
	 * @return
	 * String
	 * @throws
	 */
	public String encryText(String text, PublicKey pk) {
		try {
			Cipher cipher = Cipher.getInstance("RSA",new BouncyCastleProvider());
			cipher.init(Cipher.ENCRYPT_MODE, pk);
			int block = cipher.getBlockSize();//获取最大加密块
			int j = 0;
			StringBuilder sb = new StringBuilder();
			byte[] targetData = text.getBytes("UTF-8");
			while (targetData.length - j*block > 0) {
				byte[] jmdata = cipher.doFinal(targetData,j*block,Math.min(targetData.length - j*block, block));
				sb.append(b2hex(jmdata));
				j++;
			}
			return sb.toString();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	/**
	 * 
	 * @Title: decryText
	 * @Description: 使用默认的私钥进行解密解密算法
	 * @param text
	 * @return
	 * String
	 * @throws
	 */
	public String decryText(String text) {
		return decryText(text,getPrivateKey());
	}
	/**
	 * 
	 * @Title: decryText
	 * @Description: 指定私钥进行解密，增加对于大字符串的解密操作
	 * @param text
	 * @param privateKey2
	 * @return
	 * String
	 * @throws
	 */
	public String decryText(String text, PrivateKey pk) {
		try {
			Cipher cipher = Cipher.getInstance("RSA", new BouncyCastleProvider());
			cipher.init(Cipher.DECRYPT_MODE, pk);
			byte[] targetBuff = Hex.decodeHex(text.replace(" ", "").toCharArray());
			int block = cipher.getBlockSize();
			int j = 0;
			StringBuilder sb = new StringBuilder();
			while (targetBuff.length - j * block > 0) {
				byte[] jmdata = cipher.doFinal(targetBuff,j*block,block);
				sb.append(new String(jmdata,"UTF-8"));
				j++;
			}
			return sb.toString();
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	/**
	 * 
	 * @Title: decryTextByUrl
	 * @Description: 解密前台传递的加密串，为防止中文乱码，前台字符串最好使用encodeURIComponent方法进行url编码
	 * @param text
	 * @return
	 * String
	 * @throws
	 */
	public String decryTextByUrl(String text) {
		try {
			Cipher cipher = Cipher.getInstance("RSA", new BouncyCastleProvider());
			cipher.init(Cipher.DECRYPT_MODE, getPrivateKey());
			byte[] targetBuff = Hex.decodeHex(text.replace(" ", "").toCharArray());
			int block = cipher.getBlockSize();
			int j = 0;
			StringBuilder sb = new StringBuilder();
			while (targetBuff.length - j * block > 0) {//处理大字符串的加密解密处理
				byte[] jmdata = cipher.doFinal(targetBuff,j*block,block);
				sb.append(new StringBuilder(new String(jmdata,"UTF-8")).reverse());
				j++;
			}
			String res = URLDecoder.decode(sb.toString(), "UTF-8");
			return res;
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	/**
	 * 
	 * @Title: createPubKey
	 * @Description: 根据指定的幂和模式生成公钥
	 * @param exponent
	 * @param modules
	 * @return
	 * PublicKey
	 * @throws
	 */
	public PublicKey createPubKey(byte[] exponent,byte[]modules) {
		try {
			KeyFactory keyFactory = KeyFactory.getInstance("RSA", new BouncyCastleProvider());
			RSAPublicKeySpec rsaKs = new RSAPublicKeySpec(new BigInteger(modules),new BigInteger(exponent));
			return keyFactory.generatePublic(rsaKs);
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		return null;
	}
	/**
	 * 
	 * @Title: createPubKey
	 * @Description: 根据指定的幂和模式生成公钥
	 * @param exponent
	 * @param modules
	 * @return
	 * PublicKey
	 * @throws
	 */
	public PrivateKey createPriKey(byte[] exponent,byte[]modules) {
		try {
			KeyFactory keyFactory = KeyFactory.getInstance("RSA", new BouncyCastleProvider());
			RSAPrivateKeySpec rsaKs = new RSAPrivateKeySpec(new BigInteger(modules),new BigInteger(exponent));
			return keyFactory.generatePrivate(rsaKs);
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	/**
	 * 
	 * @Title: saveKeyToFile
	 * @Description: 保存公钥和私钥到文件中
	 * void
	 * @throws
	 */
	public void saveKeyToFile() {
		PublicKey pk = getPublicKey();
		PrivateKey prik = getPrivateKey();
		
		String path = RSAUtil.class.getClassLoader().getResource("").getPath();
		ObjectOutputStream outPub = null;
		ObjectOutputStream outPri = null;
		try {
			System.out.println(path + keyPubFile);
			outPub = new ObjectOutputStream(new FileOutputStream(path + keyPubFile));
			outPri = new ObjectOutputStream(new FileOutputStream(path + keyPriFile));
			outPub.writeObject(pk);
			outPri.writeObject(prik);
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			try {
				outPub.close();
				outPri.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			
		}
	}
	/**
	 * 
	 * @Title: readKey
	 * @Description: 读取密钥
	 * @param isPub
	 * @return
	 * Object
	 * @throws
	 */
	public Object readKey(boolean isPub) {
		String path = RSAUtil.class.getClassLoader().getResource("").getPath();
		ObjectInputStream in = null;
		try {
			if(isPub) {
				path += keyPubFile;
				in = new ObjectInputStream(new FileInputStream(path));
				PublicKey pk = (PublicKey) in.readObject();
				return pk;
			}else {
				path += keyPriFile;
				in = new ObjectInputStream(new FileInputStream(path));
				PrivateKey pk = (PrivateKey) in.readObject();
				return pk;
			}
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			try {
				in.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return null;
	}
	
	
	/**
	 * 
	 * @Title: b2hex
	 * @Description: 将二进制转为16进制字符串
	 * @param buff
	 * @return
	 * String
	 * @throws
	 */
	public String b2hex(byte[] buff) {
		StringBuilder sb = new StringBuilder();
		for(int i = 0; i < buff.length; i++) {
			int z = buff[i];
			if(z < 0) {
				z+= 256;
			}
			if(z < 16) {
				sb.append("0");
			}
			sb.append(Integer.toHexString(z));
		}
		return sb.toString();
	}
}

